﻿namespace MefContrib.Models.Provider.Definitions.Attributed
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Reflection;
    using MefContrib.Models.Provider.Validation;

    /// <summary>
    /// 
    /// </summary>
    public class AttributedDefinitionProvider : DefinitionProvider
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AttributedDefinitionProvider"/> class, using
        /// the specified <see cref="Assembly"/>.
        /// </summary>
        public AttributedDefinitionProvider(Assembly assembly)
        {
            assembly
                .Require("assembly")
                .NotNull();

            this.Assembly = assembly;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <value></value>
        public Assembly Assembly { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        /// <value></value>
        private IEnumerable<PartDescription> Descriptions { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inspectedType"></param>
        /// <returns></returns>
        private static Collection<ExportDescription> GetExports(Type inspectedType)
        {
            const BindingFlags flags =
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;

            var exports =
                from member in inspectedType.GetMembers(flags)
                let attribute = member.GetCustomAttributes(typeof(ExportableAttribute), true).FirstOrDefault() as ExportableAttribute
                where attribute != null
                select new ExportDescription
                {
                    Contract = attribute.Contract,
                    MemberName = (member.MemberType == MemberTypes.TypeInfo) ? null : member.Name,
                    Metadata = new Dictionary<string, object>()
                };

            ExportableAttribute typeAttribute =
                inspectedType.GetCustomAttributes(typeof(ExportableAttribute), true).FirstOrDefault() as ExportableAttribute;

            if (typeAttribute != null)
            {
                ExportDescription typeExport =
                    new ExportDescription { Contract = typeAttribute.Contract };

                exports = exports.Concat(new[] { typeExport });
            }

            return new Collection<ExportDescription>(exports.ToList());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inspectedType"></param>
        /// <returns></returns>
        private static Collection<ImportDescription> GetImports(Type inspectedType)
        {
            const BindingFlags flags = 
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;

            var imports =
                from member in inspectedType.GetMembers(flags)
                let attribute = member.GetCustomAttributes(typeof(ImportableAttribute), true).FirstOrDefault() as ImportableAttribute
                where attribute != null
                select new ImportDescription
               {
                   AllowDefault = attribute.AllowDefault,
                   Contract = attribute.Contract,
                   MemberName = member.Name,
                   Prerequisite = true,
                   Recomposable = false,
                   RequiredMetadata = new Collection<string>()
               };

            return new Collection<ImportDescription>(imports.ToList());
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private IEnumerable<PartDescription> GetPartDescriptions()
        {
            foreach (var type in this.Assembly.GetTypes())
            {
                PartDescription part =
                    new PartDescription(type.AssemblyQualifiedName, GetExports(type), GetImports(type));

                if (part.Exports.Count() > 0 || part.Imports.Count() > 0)
                {
                    yield return part;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <value></value>
        public override IEnumerable<PartDescription> Parts
        {
            get
            {
                if (this.Descriptions == null)
                {
                    this.Descriptions = this.GetPartDescriptions();
                }

                return this.Descriptions;
            }
        }
    }
}